#include"OrdenadorExterno.h"
#include<cstdlib>
#include<climits>
#include<algorithm>

using std::min;

void OrdenadorExterno::alternarBufferOrdenamiento(){
    if(this->bufferActual== 0){
        this->bufferActual= 1;
        return;
    }
    this->bufferActual= 0;
}//alternarBuffer


void OrdenadorExterno::setTamaniosBuffers(unsigned int tamanioBufferLectura){
    unsigned int tbl;
    if(!tamanioBufferLectura)
        tbl= this->TAMANIO_BUFFER_LECTURA_DEFAULT;

    this->tamanioBufferLectura= tbl;
    this->tamanioBufferEscritura= tbl;
    this->tamanioBufferOrdenamiento= 2*tbl;
}//setTamaniosBuffers


short OrdenadorExterno::setArchivoEntrada(string direccionArchivoEntrada){
    if(this->archivoEntrada->is_open())
        this->archivoEntrada->close();
    this->archivoEntrada->open(direccionArchivoEntrada.c_str(),ifstream::binary);

    if(!this->archivoEntrada->is_open())
        return ARCHIVO_ENTRADA_INVALIDO;

    this->archivoEntrada->seekg(0,ifstream::end);
    this->cantidadPalabrasAleatoriasIniciales= (unsigned long)(archivoEntrada->tellg()/sizeof(PalabraAleatoria));
    this->cantidadPalabrasAleatoriasSinOrdenar= this->cantidadPalabrasAleatoriasIniciales;
    this->archivoEntrada->seekg(0,ifstream::beg);
    return EXITO;

}//setArchivoEntrada


void OrdenadorExterno::llenarBufferOrdenamiento(){
    unsigned long cantidadBloquesLectura;
    /*cantidad de bloques de palabras aleatorias de tamanio "tamanioBufferLectura" 
     que aun quedan por leer del archivo*/
    unsigned long restoPalabrasAleatorias;
    /*cantidad de palabras aleatorias que sobran (si la cantidad de palabras aleatorias
     en el archivo no es multiplo del tamanio de buffer de lectura)*/
    
    restoPalabrasAleatorias= cantidadPalabrasAleatoriasSinOrdenar%tamanioBufferLectura;
    cantidadBloquesLectura= (unsigned long)(cantidadPalabrasAleatoriasSinOrdenar/tamanioBufferLectura);
    /*determino cantidad de bloques de lectura necesarios para levantar todos los registros del archivo
     restantes*/

    /*si la cantidad de bloques de lectura es mayor o igual a 10
    se llenara el buffer de ordenamiento y aun quedaran registros en el archivo*/
    if(cantidadBloquesLectura>=MAGNITUD_BUFFER_ORDENAMIENTO){
        for(unsigned int i=0;i<MAGNITUD_BUFFER_ORDENAMIENTO;i++){
            archivoEntrada->read((char*)bufferLectura,tamanioBufferLectura*sizeof(PalabraAleatoria));
            for(unsigned int j=0;j<this->tamanioBufferLectura;j++){
                this->buffersOrdenamiento[this->bufferActual]->insertar(bufferLectura[j]);
            }//for j
        }//for i
        this->cantidadPalabrasAleatoriasSinOrdenar-= MAGNITUD_BUFFER_ORDENAMIENTO*tamanioBufferLectura;
        return;
    }//if_cantidadBloquesLectura

    /* en caso que cantidadBloquesLectura<10, el buffer de ordenamiento no quedara
     completamente lleno, */

    for(unsigned int i=0;i<cantidadBloquesLectura;i++){
        this->archivoEntrada->read((char*)bufferLectura,tamanioBufferLectura*sizeof(PalabraAleatoria));
            for(unsigned int j=0;j<this->tamanioBufferLectura;j++){
                this->buffersOrdenamiento[this->bufferActual]->insertar(bufferLectura[j]);
            }//for j
    }//for i

    this->archivoEntrada->read((char*)bufferLectura,restoPalabrasAleatorias*sizeof(PalabraAleatoria));
    for(unsigned int i=0;i<restoPalabrasAleatorias;i++)
        this->buffersOrdenamiento[this->bufferActual]->insertar(bufferLectura[i]);

    this->cantidadPalabrasAleatoriasSinOrdenar-= cantidadBloquesLectura*this->tamanioBufferLectura;
    this->cantidadPalabrasAleatoriasSinOrdenar-= restoPalabrasAleatorias;
    /*deberia quedar en cero*/

}//llenarHeap


void OrdenadorExterno::ordenarBuffer(unsigned long cpa_bufferLectura){
    string nombreRunActual= NOMBRE_RUN;
    nombreRunActual+= this->i_runActual;
    ofstream runActual(nombreRunActual.c_str());
    if(!runActual.is_open())
        return;
    
    PalabraAleatoria pa_buffersOrdenamiento;/*palabraAleatoriaBuffersOrdenamiento
    /*es la palabraAleatoria que se levanta del buffer de ordenamiento actual*/
    
    unsigned long i_bufferEscritura= 0;/*indiceBufferEscritura
    al llenarse el buffer, debera ser desagotado en el run*/
    
    string comandoMovimientoRun;
    /*comando para mover el archivo del run terminado al directorio de runs*/
    
    const PalabraAleatoria PA_MAX("zzzzz",ULONG_MAX);
    /*"maxima" palabra aleatoria, valor de control de caracter comparativo*/
    
    PalabraAleatoria pa_bufferLectura;
    /*siguiente PalabraAleatoria leida del buffer de lectura*/
    
    unsigned long i_bufferLectura= 0;
    /*indice actual del buffer de lectura */
    
    while(i_bufferLectura<cpa_bufferLectura){
        
        this->buffersOrdenamiento[bufferActual]->remover(pa_buffersOrdenamiento);
        this->bufferEscritura[i_bufferEscritura]= pa_buffersOrdenamiento;
        /*se remueve un elemento del buffer de ordenamiento actual y se coloca en
         el buffer de escritura*/
        
        i_bufferEscritura++;
        if(i_bufferEscritura>= this->tamanioBufferEscritura){
            runActual.write((char*)bufferEscritura,tamanioBufferEscritura*sizeof(PalabraAleatoria));
            i_bufferEscritura= 0;
            /*vacio el buffer de escritura en el runActual si el mismo se lleno*/
        }//if i_bufferEscritura
        
        if(this->buffersOrdenamiento[bufferActual]->estaVacio()){
            this->alternarBufferOrdenamiento();
            if(i_bufferEscritura)
                runActual.write((char*)bufferEscritura,i_bufferEscritura*sizeof(PalabraAleatoria));
            i_bufferEscritura= 0;
            /*en caso que el buffer de ordenamiento actual este vacio (lo que implica
             que el alternativo esta lleno), entonces se alterna el buffer de ordenamiento
             y se desagota el buffer de escritura en el run actual.*/
            
            runActual.close();
            comandoMovimientoRun= "mv ";
            comandoMovimientoRun+= nombreRunActual+" "+NOMBRE_DIRECTORIO_RUNS;
            system(comandoMovimientoRun.c_str());
            /*el run actual se cierra y el archivo se mueve al directorio de runs*/
            
            this->i_runActual++;
            nombreRunActual= NOMBRE_RUN;
            nombreRunActual+= i_runActual;
            runActual.open(nombreRunActual.c_str());
            pa_buffersOrdenamiento= PA_MAX;
            /*se crea un nuevo run .*/
            
        }//if buffersOrdenamiento
        else{
            pa_bufferLectura= this->bufferLectura[i_bufferLectura];
            if(pa_bufferLectura<pa_buffersOrdenamiento){
                this->alternarBufferOrdenamiento();
                this->buffersOrdenamiento[bufferActual]->insertar(pa_bufferLectura);
                this->alternarBufferOrdenamiento();
                /*si el elemento siguiente del buffer de lectura es menor que el 
                 ultimo elemento removido del buffer de ordenamiento, el mismo es
                 insertado en el buffer de ordenamiento alternativo (es congelado)*/
            }
            i_bufferLectura++;     
            
        }//else
        
        /*la secuencia de escape de este ciclo ocurre unicamente cuando el buffer 
         de lectura se encuentra vacio y el de ordenamiento esta No vacio*/
        
    }//while i_bufferLectura
       
    
    /*ahora el buffer de lectura se encuentra vacio, se deben desagotar los buffers
     de ordenamiento y escritura y terminar de escribir los runs*/
    for(int i_bufferOrdenamiento=0;i_bufferOrdenamiento<2;i_bufferOrdenamiento++){
        
        while(!this->buffersOrdenamiento[bufferActual]->estaVacio()){
            
            this->buffersOrdenamiento[bufferActual]->remover(pa_buffersOrdenamiento);
            this->bufferEscritura[i_bufferEscritura]= pa_buffersOrdenamiento;

            i_bufferEscritura++;
            if(i_bufferEscritura>= this->tamanioBufferEscritura){
                runActual.write((char*)bufferEscritura,tamanioBufferEscritura*sizeof(PalabraAleatoria));
                i_bufferEscritura= 0;
                /*vacio el buffer de escritura en el runActual*/
            }//if i_bufferEscritura
                        
            
        }//while 
        
        if(i_bufferEscritura)
            runActual.write((char*)bufferEscritura,i_bufferEscritura*sizeof(PalabraAleatoria));
        i_bufferEscritura= 0;
        if(runActual.is_open()){
            runActual.close();
            comandoMovimientoRun= "mv ";
            comandoMovimientoRun+= nombreRunActual+" "+NOMBRE_DIRECTORIO_RUNS;
            system(comandoMovimientoRun.c_str());
            this->i_runActual++;
        }
        
        this->alternarBufferOrdenamiento();
        if(!this->buffersOrdenamiento[bufferActual]->estaVacio()){
            nombreRunActual= NOMBRE_RUN;
            nombreRunActual+= i_runActual;
            runActual.open(nombreRunActual.c_str());
            /*verifico de antemano si en el buffer alternativo quedan registros.
             en caso afirmativo creo un nuevo run*/
        }
        
    }//for i_bufferOrdenamiento
    
    
}//ordenarBuffer


void OrdenadorExterno::generarRuns(){
    string comandoCreacionDirectorio= "mkdir ";
    comandoCreacionDirectorio+=NOMBRE_DIRECTORIO_RUNS;
    system(comandoCreacionDirectorio.c_str());
    /*genero el directorio donde estaran almacenados los runs*/
    
    unsigned long cpa_bufferLectura;
    /*cantidad de palabras aleatorias a insertar en el buffer de lectura*/
    while(this->cantidadPalabrasAleatoriasSinOrdenar>0){
        
        this->llenarBufferOrdenamiento();
        cpa_bufferLectura= min((unsigned long)tamanioBufferLectura,cantidadPalabrasAleatoriasSinOrdenar);
        /*determino cantidad de palabras aleatorias a leer desde el archivo de entrada al buffer de 
         lectura*/
        this->archivoEntrada->read((char*)bufferLectura,cpa_bufferLectura*sizeof(PalabraAleatoria));
        this->ordenarBuffer(cpa_bufferLectura);
        this->cantidadPalabrasAleatoriasSinOrdenar-= cpa_bufferLectura;        
        
    }
}//generarRuns

short OrdenadorExterno::ordenar(string direccionArchivoSalida){
    if(!this->archivoEntrada->is_open())
        return ARCHIVO_ENTRADA_INVALIDO;
    
    this->generarRuns();
}//ordenar


OrdenadorExterno::OrdenadorExterno(unsigned int tamBufferLectura):NOMBRE_RUN("run_"),NOMBRE_DIRECTORIO_RUNS("runs/"){
    this->setTamaniosBuffers(tamBufferLectura);
    this->buffersOrdenamiento= new Heap<PalabraAleatoria>* [2];
    for(int i=0;i<2;i++)
        this->buffersOrdenamiento[i]= new Heap<PalabraAleatoria>(this->tamanioBufferLectura*MAGNITUD_BUFFER_ORDENAMIENTO);
    this->archivoEntrada= new ifstream;
    this->bufferEscritura= new PalabraAleatoria[this->tamanioBufferEscritura];
    this->bufferLectura= new PalabraAleatoria[this->tamanioBufferLectura];
    this->bufferActual= 0;
    this->cantidadPalabrasAleatoriasSinOrdenar= 0;
    this->cantidadPalabrasAleatoriasIniciales= 0;
    this->i_runActual= 65;
}//constructor


OrdenadorExterno::~OrdenadorExterno(){
    for(int i=0;i<2;i++)
        delete this->buffersOrdenamiento[i];
    delete[] this->buffersOrdenamiento;
    if(this->archivoEntrada->is_open())
        this->archivoEntrada->close();
    delete this->archivoEntrada;
    delete[] this->bufferEscritura;
    delete[] this->bufferLectura;

}//destructor
